#ifndef _SHPTR_H_
#define _SHPTR_H_

#include <sys/types.h>
#include <sys/shm.h>

#include <string>
#include <sstream>

#include "../IO/log.h"

/**
 * Un objeto de esta clase representa un área de memoria compartida.
 * La clase está diseñada de forma tal que un objeto SharedPointer<T, n>
 * es equivalente a un puntero de tipo T* apuntando a un área de
 * tamaño sizeof(T)*n bytes. Por ejemplo:
 *
 *    SharedPointer<char, 12> string;
 *    string.allocate();
 *    memcpy(string, "Hola Mundo!");
 *    std::cout << string << "\n";
 *    string.free();
 *
 */
template <class T, int n=1>
class SharedPointer {
	int shmid;
	T* ptr;

public:
    /**
     * Crea un SharedPointer sin memoria reservada, apuntando a NULL.
     */
    SharedPointer():
        shmid(-1),
        ptr(NULL) {
    }

    /**
     * Reserva sizeof(T)*n bytes en un área compartida de memoria.
     */
	bool allocate() {
		if( (shmid = shmget(IPC_PRIVATE, sizeof(T)*n, 0600)) == -1) {
			return false;
		}

		if( (ptr = (T*) shmat(shmid, 0, 0)) == (void*)-1) {
			shmctl(shmid, IPC_RMID, 0);
			return false;
		}

		shmctl(shmid, IPC_RMID, 0);

		return true;
	}

    /**
     * Indica que el área de memoria apuntada por el SharedPointer ya no
     * será usada por este proceso. Cuando el último proceso que estaba
     * utilizando el área de memoria realiza el correspondiente free(),
     * se liberan los recursos tomados en el sistema operativo.
     */
	void free() {
	    shmdt(ptr);
		shmid = -1;
		ptr = NULL;
	}

    /**
     * Devuelve true si y solo si el SharedPointer tiene memoria reservada.
     */
	operator bool() {
		return shmid != -1;
	}

    /**
     * Sobreescribir el operador de desreferencia permite operar
     * con el SharedPointer como si fuera un puntero de tipo T*
     */
	T& operator*() {
		return *ptr;
	}

    /**
     * Sobreescribir el operador "flechita" permite operar
     * con el SharedPointer como si fuera un puntero de tipo T*
     */
	T* operator->() {
		return ptr;
	}

    /**
     * Sobreescribir el operador de casting a T* permite operar
     * con el SharedPointer como si fuera un puntero de tipo T*
     */
	operator T*() {
		return ptr;
	}

    /**
     * Sobreescribir el operador de casting a void* permite operar
     * con el SharedPointer como si fuera un puntero de tipo void*
     */
	operator void*() {
		return ptr;
	}
};

#endif
